package com.panopset.game.blackjack;

import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import com.panopset.compat.Strings;
import com.panopset.compat.Util;
import com.panopset.compat.Zombie;
import com.panopset.game.card.Shoe;

/**
 * Blackjack game engine.
 *
 * @author Karl Dinwiddie
 *
 */
public final class BlackjackGame {

    public String mistakeMessage = "";
    public String simpleMessage = "";

    private void mistakeAlert(final String msg) {
        mistakeMessage = msg;
    }

    public void simpleAlert(final String msg) {
        simpleMessage = msg;
    }

    private void clrmsg() {
        mistakeMessage = "";
        simpleMessage = "";
    }

    public static CountingSystem countingSystem;

    public BlackjackGame(final BlackjackConfig config) {
        this.c = config;
        countingSystem = config.getCountingSystems().getSelected();
        reset();
    }

    /**
     * Hit.
     */
    private void hit() {
        Hand hand = getCycle().getActivePlayerHand();
        if (hand == null) {
            simpleAlert(c.getHandNotActiveMsg());
        } else if (!hand.hasCards()) {
            simpleAlert(c.getHandNotDealtMsg());
        } else if (hand.isSplitAce()) {
            // Okay not to translate, will never get here because
            // hand will be done on a split ace.
            simpleAlert(Util.x("Can not hit a split ace."));
            return;
        } else {
            hand.deal(new BlackjackCard(getShoe().deal()));
            if (hand.isBusted()) {
                hand.setMessage(c.getBustedMsg());
                hand.clearHandChips();
            }
        }
    }

    /**
     * Stand.
     */
    private void stand() {
        Hand hand = getCycle().getActivePlayerHand();
        if (hand == null) {
            simpleAlert(c.getHandNotActiveMsg());
        } else {
            hand.stand();
        }
    }

    /**
     * Surrender.
     */
    private void surrender() {
        Hand hand = getCycle().getActivePlayerHand();
        if (hand == null) {
            simpleAlert(c.getHandNotActiveMsg());
        } else {
            hand.surrender();
        }
    }

    /**
     * Dbl.
     */
    private void dbl() {
        Player player = getCycle().getActivePlayer();
        if (player == null) {
            simpleAlert(c.getHandNotActiveMsg());
            return;
        }
        Hand hand = player.getActiveHand();
        if (hand == null) {
            simpleAlert(c.getDoubleImpossibleMsg());
            return;
        }
        if (!hand.isInitialHand()) {
            simpleAlert(c.getDoubleImpossibleMsg());
            return;
        }
        if (!hand.canDouble()) {
            simpleAlert(c.getDoubleImpossibleMsg());
        }
        if (hand.hasSplit && hand.isSplitAce()) {
            simpleAlert(c.getDoubleImpossibleMsg());
            return;
        }
        hand.deal(new BlackjackCard(getShoe().deal()));
        hand.stand();
        hand.setHandChips(hand.getChips()*2);
        hand.setDoubleDowned(true);
    }

    private Bankroll bankroll;

    public Bankroll getBankroll() {
        if (bankroll == null) {
            bankroll = new Bankroll(getConfig());
        }
        return bankroll;
    }

    public BlackjackCard deal() {
        return new BlackjackCard(getShoe().deal());
    }

    public boolean isReset() {
        return !ct.isActive();
    }

    /**
     * Split.
     */
    private synchronized void split() {
        Player player = getCycle().getActivePlayer();
        if (player == null) {
            simpleAlert(c.getHandNotActiveMsg());
            return;
        }
        Hand hand = player.getActiveHand();
        if (hand == null) {
            simpleAlert(c.getHandNotActiveMsg());
            return;
        }
        String splitMsg = hand.getCantSplitMessageIfCantSplit(c);
        if (Strings.isPopulated(splitMsg)) {
            simpleAlert(splitMsg);
            return;
        }
        int i = hand.getChips();
        Hand splitHand = new Hand(i, c, getBankroll());
        final BlackjackCard splitCard = hand.remove(1);
        hand.deal(deal());
        hand.hasSplit = true;
        splitHand.deal(splitCard);
        splitHand.deal(deal());
        splitHand.hasSplit = true;
        if (splitCard.isAce()) {
            hand.setSplitAce(c.isResplitAcesAllowed());
            splitHand.setSplitAce(c.isResplitAcesAllowed());
        }
        player.getAllHands().add(splitHand);
    }

    private Goofy goofy;

    public Goofy getGoofy() {
        if (goofy == null) {
            goofy = new Goofy();
        }
        return goofy;
    }

    /**
     * Reset.
     */
    private synchronized void reset() {
        getGoofy().reset();
        getBankroll().reset();
        ct.reset();
        if (BlackjackGame.countingSystem != null) {
            BlackjackGame.countingSystem.reset();
        }
        shoe = null;
        resetNextBet();
        getShoe().shuffle();
    }

    /**
     * Increase.
     */
    private void increase() {
        setNextBet(getNextBet() + c.getWagerIncrement());
    }

    /**
     * Decrease.
     */
    private void decrease() {
        setNextBet(getNextBet() - c.getWagerIncrement());
    }

    /**
     * Event thread.
     */
    private Thread automatic;

    public boolean isAutomatic() {
        return automatic != null;
    }

    private boolean fromVariations = false;

    public void startFullAutomaticFromVariations() {
        fromVariations = true;
        auto();
    }
    /**
     * Auto.
     */
    private synchronized void auto() {
        if (automatic == null) {
            automatic = new Thread(new Runnable() {

                @Override
                public void run() {
                    while (isAutomatic() && Zombie.isActive()) {


                        RecommendedAction ra = getCycle().getRecommendedAction();

                        String rac = ra.getCommand();

                        if (fromVariations && ra.isVariableDecision(getCycle().getActivePlayerHand())) {
                            automatic = null;
                            fromVariations = false;
                            return;
                        }


                        if (CMD_DEAL.equals(rac)) {
                            if ((getConfig().getTargetStake() > 0) && (getBankroll().getTotalStakeCashBalancePlusChips() >= getConfig()
                                    .getTargetStake())) {
                                automatic = null;
                                return;
                            }
                            if (getCycle().isDealt()) {
                                if (getCycle().isActive()) {
                                    if (getCycle().getActivePlayerHand() == null) {
                                        return;
                                    }
                                }
                            }
                        }
                        exec(rac);
                    }
                }

            });
            automatic.start();
        } else {
            automatic = null;
        }
    }

    /**
     * Deal cards command.
     */
    public static final String CMD_DEAL = "l";

    /**
     * Surrender command.
     */
    public static final String CMD_SURRENDER = "u";

    /**
     * Hit command.
     */
    public static final String CMD_HIT = "h";

    /**
     * Stand command.
     */
    public static final String CMD_STAND = "s";

    /**
     * Double command.
     */
    public static final String CMD_DOUBLE = "d";

    /**
     * Split command.
     */
    public static final String CMD_SPLIT = "p";

    /**
     * Reset command
     */
    public static final String CMD_RESET = "r";

    /**
     * Increase bet command.
     */
    public static final String CMD_INCREASE = "i";

    /**
     * Decrease bet command.
     */
    public static final String CMD_DECREASE = "e";

    /**
     * Automatic play command.
     */
    public static final String CMD_AUTO = "t";

    /**
     * Show/hide count.
     */
    public static final String CMD_COUNT = "c";

    /**
     * Shuffle cards.
     */
    public static final String CMD_SHUFFLE = "f";

    public void shuffle() {
        getShoe().shuffle();
        if (BlackjackGame.countingSystem != null) {
            BlackjackGame.countingSystem.reset();
        }

        if (Strings.isPopulated(c.getStackedDeck())) {
            getShoe().stack(c.getStackedDeck());
            simpleAlert("Shuffled and stacked deck for debugging");
        } else {
            simpleAlert(c.getShuffledMsg());
        }

    }

    /**
     * @param action
     *            Action to execute.
     */
    public synchronized void thereIsNoTryOnlyDo(final String action) {
        clrmsg();
        if (!Strings.isPopulated(action)) {
            return;
        }
        if (CMD_RESET.equals(action)) {
            reset();
            simpleAlert(c.getResetMsg());
            return;
        } else if (CMD_COUNT.equals(action)) {
            c.toggleShowCount();
            return;
        } else if (CMD_SHUFFLE.equals(action)) {
            if (ct.isActive() && ct.isDealt()) {
                simpleAlert(c.getHandActiveMsg() + ".");
                return;
            }
            shuffle();
            return;
        } else if (CMD_INCREASE.equals(action)) {
            increase();
            return;
        } else if (CMD_DECREASE.equals(action)) {
            decrease();
            return;
        } else if (CMD_AUTO.equals(action)) {
            auto();
            return;
        }
        String recommendedActionCommand = getCycle().getRecommendedAction().getCommand();
        if (CMD_DEAL.equals(recommendedActionCommand) && !CMD_DEAL.equals(action)) {
            simpleAlert(c.getPleaseSelectMsg() + " L=" + c.getDealMsg());
            return;
        }
        String recommendedActionStrategyLine = "";
        if (!action.equals(recommendedActionCommand)) {
            recommendedActionStrategyLine = getCycle().getStrategyLine();
        }
        if (CMD_DEAL.equals(action)) {
            if (getShoe().remaining() < 30) {
                shuffle();
                BlackjackGame.countingSystem.reset();
            }
            if (getCycle().isDealt()) {
                if (getCycle().isActive()) {
                    simpleAlert(c.getHandActiveMsg());
                    return;
                } else {
                    ct.reset();
                }
            }
            getCycle().deal();
        } else if (CMD_SURRENDER.equals(action)) {
            surrender();
        } else if (CMD_HIT.equals(action)) {
            hit();
        } else if (CMD_STAND.equals(action)) {
            stand();
        } else if (CMD_DOUBLE.equals(action)) {
            dbl();
        } else if (CMD_SPLIT.equals(action)) {
            split();
        }
        if (!"".equals(simpleMessage)) {
            return;
        }
        if (Strings.isPopulated(recommendedActionStrategyLine)) {
            mistakeAlert(recommendedActionStrategyLine);
            getGoofy().rat();
        }
    }

    private ExecListener el;

    public void setExecListener(ExecListener execListener) {
        el = execListener;
    }

    /**
     * @param action
     *            Action to execute.
     */
    public synchronized void exec(final String action) {
        thereIsNoTryOnlyDo(action);
        if (getCycle().getActivePlayerHand() == null) {
            getCycle().finish();
            if (getConfig().isFastDeal() && automatic == null
                    && "".equals(mistakeMessage)) {
                exec(CMD_DEAL);
            }
        }
        if (el != null) {
            el.update();
        }
    }

    private Shoe shoe;

    public Shoe getShoe() {
        if (shoe == null) {
            shoe = new Shoe(c.getDecks());
            if (Strings.isPopulated(c.getStackedDeck())) {
                getShoe().stack(c.getStackedDeck());
            }
        }
        return shoe;
    }

    /**
     * Config
     */
    private final BlackjackConfig c;

    public BlackjackConfig getConfig() {
        return c;
    }

    /**
     * Cycle controller.
     */
    public static final CycleController ct = new CycleController();

    /**
     *
     * @return
     */
    public synchronized Cycle getCycle() {
        return ct.getCycle(this, getShoe());
    }

    private Integer nextBet;

    public Integer getNextBet() {
        if (nextBet == null) {
            nextBet = c.getWagerIncrement();
        }
        return nextBet;
    }

    public void setNextBet(final int value) {
        if (value > 0) {
            nextBet = value;
        } else {
            nextBet = c.getWagerIncrement();
        }
        Cycle cycle = getCycle();
        if (cycle == null) {
            return;
        }
        cycle.setNextBet(value, getBankroll());
    }

    public void resetNextBet() {
        nextBet = null;
    }

    public List<String> getGameStatusVertical() {
        List<String> rtn = new ArrayList<String>();
        StringTokenizer st = new StringTokenizer(getRawGameStatus(), "|");
        while (st.hasMoreElements()) {
            rtn.add(st.nextToken());
        }
        return rtn;
    }

    public String getGameStatusHorizontal() {
        return getRawGameStatus().replace("|", "");
    }


    public List<String> getStatusChipsVertical() {
        List<String> rtn = new ArrayList<String>();
        StringTokenizer st = new StringTokenizer(getRawStatusChips(), "|");
        while (st.hasMoreElements()) {
            rtn.add(st.nextToken());
        }
        return rtn;
    }

    public String getStatusChipsHorizontal() {
        return getRawStatusChips().replace("|", "");
    }

    public String getRawGameStatus() {
        StringWriter sw = new StringWriter();
//        sw.append(getRawStatusChips());
//        sw.append("|  Chips: "
//                + Strings.getDollarString(getBankroll().getChips()));
        sw.append("| Stake: "
                + Strings.getDollarString(getBankroll()
                        .getTotalStakeCashBalancePlusChips()));
        sw.append("|  Score: " + getGoofy().getHandsSinceLastMistake());
        sw.append(" (" + getGoofy().getHandsSinceLastMistakeRecord() + ")");
        if (getConfig().isShowCount()) {
            sw.append("| ");
            sw.append(getConfig().getCountingSystems().getSelected().getName());
            sw.append(": "
                    + getConfig().getCountingSystems().getSelected().getCount());
        }
        return sw.toString();
    }

    public String getRawStatusChips() {
        StringWriter sw = new StringWriter();
        sw.append("  reloads: " + getBankroll().getReloadCount());
        sw.append("|  Chips: " + Strings.getDollarString(getBankroll().getTotalChips()));
        sw.append("|  Next bet: " + Strings.getDollarString(getNextBet()));
        return sw.toString();
    }

    public void reportNewHand() {
        if (isAutomatic()) {
            getGoofy().reportNewHandAutomatic();
        } else {
            getGoofy().reportNewHand();
        }
    }
}
